package com.punch.oauth.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.punch.common.constant.CommonConstant;
import com.punch.common.constant.SecurityConstants;
import com.punch.common.constant.WXURLConstant;
import com.punch.common.dto.user.user.RedisUserDTO;
import com.punch.common.dto.login.WXUserDTO;
import com.punch.common.exception.BusinessException;
import com.punch.common.exception.CommonExceptionEnum;
import com.punch.common.feign.FeignGuardianService;
import com.punch.common.simple.redis.utils.RedisUtil;
import com.punch.common.utils.ResponseUtil;
import com.punch.common.wx.crypto.WxCryptUtils;
import com.punch.oauth.constant.ClientConstant;
import com.punch.oauth.dto.LoginUserDTO;
import com.punch.oauth.dto.WxCodeDTO;
import com.punch.oauth.dto.WxLoginDTO;
import com.punch.oauth.exception.UaaExceptionEnum;
import com.punch.oauth.mobile.MobileCodeAuthenticationToken;
import com.punch.oauth.openid.OpenIdAuthenticationToken;
import com.punch.oauth.service.impl.RedisClientDetailsService;
import com.punch.oauth.vo.LoginUserVO;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

/**
 * 获取RSA公钥接口
 * Created by macro on 2020/6/19.
 */
@RestController
@Slf4j
public class LoginController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Resource
    private AuthorizationServerTokenServices authorizationServerTokenServices;


    @Resource
    private ObjectMapper objectMapper;


    @Autowired
    private RedisClientDetailsService clientDetailsService;

    @Autowired
    private RedisUtil redisUtil;


    @Value("${punch.wx.appid}")
    private String wxAppid;

    @Value("${punch.wx.secret}")
    private String wxSecret;

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private FeignGuardianService feignGuardianService;


    /**
     * 通过用户手机号码与密码获取token
     *
     * @param loginUserDTO 登录表单
     * @param request      请求
     * @param response     响应
     * @throws IOException
     */
    @ApiOperation(value = "用户名密码获取token")
    @PostMapping(value = "/oauth/user/login", produces = "application/json;charset=UTF-8")
    public void getUserTokenInfo(@RequestBody LoginUserDTO loginUserDTO, HttpServletRequest request, HttpServletResponse response) throws IOException {
        String clientType = request.getHeader("clientType");
        if (ClientConstant.WEBAPP.equals(clientType)) {
            if (StringUtils.isEmpty(loginUserDTO.getPassword())) {
                throw new UnapprovedClientAuthenticationException("密码为空");
            }
            String uuid = request.getHeader("uuid");
            checkCode(loginUserDTO.getCaptcha(), uuid);
        }
        if (ClientConstant.H5.equals(clientType)) {
            loginUserDTO.setPassword("123456");
        }
        String user = loginUserDTO.getMobile() + "-" + clientType;
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, loginUserDTO.getPassword());
        LoginUserVO loginUserVO = new LoginUserVO();
        writerToken(clientType, response, token, loginUserVO);
    }


    /**
     * 根据手机验证获取token登录
     *
     * @param loginUserDTO 登录表单
     * @param response     响应
     * @throws IOException
     */
    @ApiOperation(value = "手机号和验证码获取token")
    @PostMapping(value = "/oauth/user/code/token", produces = "application/json;charset=UTF-8")
    public void getUserTokenByCode(@RequestBody LoginUserDTO loginUserDTO, HttpServletResponse response) throws IOException {
        if (StringUtils.isEmpty(loginUserDTO.getCode())) {
            throw new UnapprovedClientAuthenticationException("验证码为空");
        }
        MobileCodeAuthenticationToken token = new MobileCodeAuthenticationToken(loginUserDTO.getMobile());
        //验证验证码
        if (!verifyAuthCode(loginUserDTO.getCode(), loginUserDTO.getMobile())) {
            throw new BusinessException(1, "验证码错误");
        }
        LoginUserVO loginUserVO = new LoginUserVO();
        writerToken(ClientConstant.WEBAPP, response, token, loginUserVO);
    }

    /**
     * 获取用户openId
     *
     * @param wxCodeDTO 微信获取openid
     * @param response  响应
     * @throws IOException
     */
    @ApiOperation(value = "获取用户openId")
    @PostMapping(value = "/oauth/wx-user/code", produces = "application/json;charset=UTF-8")
    public void getWxOpenidByWxCode(@RequestBody WxCodeDTO wxCodeDTO, HttpServletResponse response) throws IOException {
        if (StringUtils.isEmpty(wxCodeDTO.getJsCode())) {
            throw new UnapprovedClientAuthenticationException("微信code为空");
        }
        //发送接口给微信
        Map<String, String> uriVariables = CollUtil.newHashMap(3);
        uriVariables.put("appid", wxAppid);
        uriVariables.put("secret", wxSecret);
        uriVariables.put("jsCode", wxCodeDTO.getJsCode());
        ResponseEntity<JSONObject> exchange = restTemplate.getForEntity(WXURLConstant.LOGIN_JSCODE2SESSION, JSONObject.class, uriVariables);
        log.info("小程序通过code获取openid返回内容:{}", exchange);
        //说明请求成功
        if (exchange.getBody().getInteger("errcode") == null) {
            //用户openId
            String openId = exchange.getBody().getString("openid");
            String sessionKey = exchange.getBody().getString("session_key");
            //家长端
            if (0 == wxCodeDTO.getType()) {
                //把openId与sessionkey存放到redis中
                String key = IdUtil.fastUUID();
                String value = openId + ";" + sessionKey;
                redisUtil.setValue(CommonConstant.WX_USER_CODE_PREFIX + key, value, 7200L);
                ResponseUtil.responseSucceed(objectMapper, response, key);
            }
//            //教师端
//            if (1 == wxCodeDTO.getType()) {
//                //查看老师是否绑定过身份
//                LoginUser teacher = feignTeacherService.getFeignTeacherByOpenId(openId);
//                JSONObject jsonObject = new JSONObject();
//                if (teacher.getUserId() == 0L) {
//                    //说明没有绑定过身份，需要跳转绑定身份页面
//                    jsonObject.put("bindFlag", false);
//                } else {
//                    //绑定过身份
//                    jsonObject.put("bindFlag", true);
//                }
//                String key = IdUtil.fastUUID();
//                String value = openId + ";" + sessionKey;
//                redisUtil.setValue(CommonConstant.WX_USER_CODE_PREFIX + key, value, 7200L);
//                jsonObject.put("uuid", key);
//                ResponseUtil.responseSucceed(objectMapper, response, jsonObject);
//            }
        } else {
            log.error("小程序通过code获取openid错误:{}", exchange.getBody());
            throw new BusinessException(UaaExceptionEnum.WX_LOGIN_ERROR);
        }
    }

    /**
     * 微信用户登录
     *
     * @param wxLoginDTO 登录时表单
     * @param response   响应
     * @throws IOException
     */
    @ApiOperation(value = "微信用户登录")
    @PostMapping(value = "/oauth/wx-user/login", produces = "application/json;charset=UTF-8")
    public void getWxUserLogin(@RequestBody WxLoginDTO wxLoginDTO, HttpServletResponse response) {
        String value = redisUtil.getValue(CommonConstant.WX_USER_CODE_PREFIX + wxLoginDTO.getUuid()).toString();
        if (value == null) {
            throw new BusinessException("重新获取微信code");
        }
        String[] split = value.split(";");
        String openId = split[0];
        String sessionKey = split[1];
        try {
            //家长登录
            if (wxLoginDTO.getType() == 0) {
                guardianLogin(wxLoginDTO, sessionKey, openId);
            }
//            //教师登录
//            if (wxLoginDTO.getType() == 1) {
//                // TODO: 2020/8/12
//            }
            OpenIdAuthenticationToken token = new OpenIdAuthenticationToken(openId, wxLoginDTO.getType());
            LoginUserVO loginUserVO = new LoginUserVO();
            writerToken(ClientConstant.APP, response, token, loginUserVO);
            redisUtil.deleteValue(CommonConstant.WX_USER_CODE_PREFIX + wxLoginDTO.getUuid());
        } catch (Exception e) {
            log.error("微信解密用户信息错误:{}", e.getMessage());
            throw new BusinessException(UaaExceptionEnum.WX_DECRYPT_ERROR);
        }
    }

    /**
     * 家长端登录
     *
     * @param wxLoginDTO 登录信息
     * @param sessionKey 微信sessionKey
     * @param openId     微信openId
     * @throws Exception
     */
    private void guardianLogin(WxLoginDTO wxLoginDTO, String sessionKey, String openId) throws Exception {
        if (StringUtils.isEmpty(wxLoginDTO.getEncrypdata()) || StringUtils.isEmpty(wxLoginDTO.getIvdata())) {
            throw new BusinessException(CommonExceptionEnum.PARAMETER_LESS);
        }
        String wxUserInfo = WxCryptUtils.decrypt(wxLoginDTO.getEncrypdata(), wxLoginDTO.getIvdata(), sessionKey);
        if (StringUtils.isEmpty(wxUserInfo)) {
            throw new BusinessException(UaaExceptionEnum.WX_DECRYPT_ERROR);
        }
        log.info("解密微信用户数据:{}", wxUserInfo);
        JSONObject jsonObject = JSONObject.parseObject(wxUserInfo);
        String mobile = jsonObject.getString("purePhoneNumber");
        WXUserDTO wxUserDTO = new WXUserDTO();
        wxUserDTO.setMobile(mobile);
        wxUserDTO.setOpenId(openId);
        wxUserDTO.setBrand(wxLoginDTO.getBrand());
        wxUserDTO.setModel(wxLoginDTO.getModel());
        wxUserDTO.setVersion(wxLoginDTO.getVersion());
        wxUserDTO.setSystem(wxLoginDTO.getSystem());
        wxUserDTO.setPlatform(wxLoginDTO.getPlatform());
        feignGuardianService.saveFeignGuardian(wxUserDTO);
    }


    /**
     * 解密用户手机号码
     *
     * @param wxUserDTO 登录时表单
     * @param response  响应
     * @throws IOException
     */
    @ApiOperation(value = "用户进行注册,用户已经进入到小程序,然后进行注册")
    @PostMapping(value = "/oauth/wx-user/mobile", produces = "application/json;charset=UTF-8")
    public void getWxMobile(@RequestBody WXUserDTO wxUserDTO, HttpServletResponse response) throws IOException {
//        if (StringUtils.isEmpty(wxUserDTO.getMobile())) {
//            throw new UnapprovedClientAuthenticationException("手机号码为空");
//        }
//        //验证验证码
////        if (!verifyAuthCode(wxUserDTO.getCode(), wxUserDTO.getMobile())) {
////            throw new BusinessException(1, "验证码错误");
////        }
//        String openId = UserUtil.getLoginUser().getOpenId();
//        wxUserDTO.setOpenId(openId);
//        //家长登录
//        if (wxUserDTO.getType() == 0) {
//            feignGuardianService.saveFeignGuardian(wxUserDTO);
//        }
//        //培训老师登录
//        if (wxUserDTO.getType() == 1) {
//            boolean flag = feignTeacherService.saveFeignTeacher(wxUserDTO);
//            if (!flag) {
//                throw new BusinessException(1, "教师手机号码不存在");
//            }
//        }
//        OpenIdAuthenticationToken token = new OpenIdAuthenticationToken(openId, wxUserDTO.getType());
//        LoginUserVO loginUserVO = new LoginUserVO();
//        writerToken(ClientConstant.APP, response, token, loginUserVO);
    }

    /**
     * security的通过refreshToken过期获取最新token
     *
     * @param userId 用户uid
     */
    @ApiOperation(value = "security的通过refreshToken过期获取最新token")
    @PostMapping(value = "/oauth/refresh/token/{userId}", produces = "application/json;charset=UTF-8")
    public String refreshToken(@PathVariable Long userId, HttpServletRequest request) {
        System.out.println(request);
        //System.out.println(request.getId());
//        String clientId = request.getHeaders("clientId");
        String clientId = "";
        String refreshToken = (String) redisUtil.getValue("refreshToken:" + clientId + ":" + userId);
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
        TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "password");
        OAuth2AccessToken oAuth2AccessToken = authorizationServerTokenServices.refreshAccessToken(refreshToken, tokenRequest);
        redisUtil.setValue("refreshToken:" + clientId + ":" + userId, oAuth2AccessToken.getRefreshToken().getValue());
        return oAuth2AccessToken.getRefreshToken().getValue();
    }

    /**
     * 写入token到请求头中
     *
     * @param clientId 客户端id
     * @param response 响应
     * @param token    token
     * @throws IOException
     */
    private void writerToken(String clientId, HttpServletResponse response, AbstractAuthenticationToken token, LoginUserVO loginUserVO) throws IOException {
        try {
            ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
            TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "password");
            OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
            Authentication authentication = authenticationManager.authenticate(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
            OAuth2AccessToken oAuth2AccessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
            oAuth2Authentication.setAuthenticated(true);
            Map<String, Object> additionalInformation = oAuth2AccessToken.getAdditionalInformation();
            //pc端登录返回信息
            if (ClientConstant.WEBAPP.equals(clientId)) {
                response.setHeader("Authorization", "Bearer " + oAuth2AccessToken.getValue());
                Long userId = additionalInformation.get("userId") == null ? 0L : (Long) additionalInformation.get("userId");
                StringBuilder sb = new StringBuilder();
                sb.append(CommonConstant.SECURITY_REDIS_USER_PREFIX).append(ClientConstant.WEBAPP).append(":").append(userId);
                RedisUserDTO redisUserDTO = (RedisUserDTO) redisUtil.getValue(sb.toString());
                loginUserVO.setUserName(redisUserDTO.getUsername());
                loginUserVO.setAreaId(redisUserDTO.getAreaId());
                loginUserVO.setRoleId(redisUserDTO.getRoleId());
                loginUserVO.setAreaName(redisUserDTO.getAreaName());
                loginUserVO.setHeadImgUrl(redisUserDTO.getHeadImgUrl());
                loginUserVO.setAuditStatus(redisUserDTO.getAuditStatus());
                ResponseUtil.responseSucceed(objectMapper, response, loginUserVO);
//                redisUtil.setValue("refreshToken:" + clientId + ":" + userId, oAuth2AccessToken.getRefreshToken().getValue());
            }
            //小程序端登录
            if (ClientConstant.APP.equals(clientId)) {
                loginUserVO.setAuthorizationToken("Bearer " + oAuth2AccessToken.getValue());
                ResponseUtil.responseSucceed(objectMapper, response, loginUserVO);
//                Long userId = additionalInformation.get("userId") == null ? 0L : (Long) additionalInformation.get("userId");
//                redisUtil.setValue("refreshToken:" + clientId + ":" + userId, oAuth2AccessToken.getRefreshToken().getValue());
            }

            //h5登录
            if (ClientConstant.H5.equals(clientId)) {
                loginUserVO.setAuthorizationToken("Bearer " + oAuth2AccessToken.getValue());
                ResponseUtil.responseSucceed(objectMapper, response, loginUserVO);
            }

        } catch (BadCredentialsException | InternalAuthenticationServiceException e) {
            exceptionHandler(response, "用户名或密码错误");
        } catch (Exception e) {
            exceptionHandler(response, e);
        }
    }


    private void exceptionHandler(HttpServletResponse response, Exception e) throws IOException {
        log.error("exceptionHandler-error:", e);
        exceptionHandler(response, e.getMessage());
    }

    private void exceptionHandler(HttpServletResponse response, String msg) throws IOException {
        ResponseUtil.responseFailed(objectMapper, response, msg);
    }

    //对输入的验证码进行校验
    private boolean verifyAuthCode(String authCode, String mobile) {
        if (StringUtils.isEmpty(authCode)) {
            return false;
        }
        String realAuthCode = redisUtil.getValue(SecurityConstants.DEFAULT_CODE_KEY + ":" + mobile).toString();
        return authCode.equals(realAuthCode);
    }

    public String checkCode(String checkCode, String uuid) {
        StringBuilder key = new StringBuilder();
        key.append("captcha:").append(uuid);
        Object value = redisUtil.getValue(key.toString());
        if (value == null) {
            throw new BusinessException(UaaExceptionEnum.CAPTCHA_TIME_OUT);
        }
        String redisCode = String.valueOf(value);
        if (!redisCode.equals(checkCode)) {
            throw new BusinessException(UaaExceptionEnum.CAPTCHA_ERROR);
        }
        redisUtil.deleteValue(key.toString());
        return "";
    }


}


